﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Web;
using System.Text.RegularExpressions;
using log4net;
using System.Threading;
using System.Configuration;

namespace Proxy
{
    public class Connection
    {
        private HttpListenerContext m_context;
        private string m_mode;
        private BlackWhiteListManager m_manager;
        private string m_port;
        private ILog m_logger;
        private string m_externalIP;
        private string m_requestData;
        private string m_clientIP;
        
        /// <summary>
        /// Creates a new connection for an incoming request
        /// </summary>
        /// <param name="context">the context in which the request lies in</param>
        /// <param name="mode">the proxy server mode</param>
        /// <param name="manager">a reference to the BlackWhiteListManager manager</param>
        /// <param name="port">the port that the proxy server is litening to</param>
        public Connection(HttpListenerContext context, string mode, ref BlackWhiteListManager manager, string port, ref ILog logger, string externalIP)
        {
            m_context = context;
            m_mode = mode;
            m_manager = manager;
            m_port = port;
            m_logger = logger;
            m_externalIP = externalIP;
            m_clientIP = m_context.Request.RemoteEndPoint.Address.ToString();
        }

        /// <summary>
        /// the main method of each connection
        /// reads emails from request
        /// creates request
        /// checks if site is restricted
        /// checks if client ip is in whitelist
        /// and responses to the client accordingly
        /// </summary>
        /// <author>Shai Cantor</author>
        public void run()
        {
            // if there are mails in the inputStream we save them
            getMails();

            HttpWebRequest request = GetRequest();
            if (request == null)
                return;
            else if (m_manager.isRestrictedSite(request.RequestUri.AbsoluteUri))
            {
                sentToClient("<HTML><BODY><div align=\"center\"><h1> Website is Restricted</h1></div></BODY></HTML>", true);
            }
            else 
            {
                if (!m_manager.isAuthorized(m_clientIP))
                {
                    string page = readPage("RegistrationPage.htm");
                    string ipReplacedPage = page.Replace("localhost:8080", m_externalIP + ":" + m_port);
                    sentToClient(ipReplacedPage, false);
                }
                else
                {
                    if (request != null)
                    {
                        if (m_manager.exceedsSecurityRules(m_clientIP))
                            return;
                        m_manager.addToConnections(m_clientIP);
                        setHeaders(ref request);
                        HttpWebResponse response = GetResponse(request);
                        string responseString = readResponse(response);
                        sentToClient(responseString, false);
                    }
                }
            }
            

        }

        /// <summary>
        /// converts an HttpListenerRequest to HttpWebRequest
        /// by setting the headers and cookies
        /// </summary>
        /// <param name="request">the HttpWebRequest to set</param>
        /// <author>Shai Cantor</author>
        private void setHeaders(ref HttpWebRequest request)
        {
            CookieContainer container = new CookieContainer();
            System.Collections.Specialized.NameValueCollection headers = m_context.Request.Headers;
            for(int i=0; i < headers.Count; i++)
            {
                string header = headers.Keys[i];
                string value = headers[i];
                switch (header)
                {
                    case "Accept":
                        request.Accept = value;
                        break;
                    case "Connection":
                        request.Connection = value;
                        break;
                    case "Content-Length":
                        request.ContentLength = long.Parse(value);
                        break;
                    case "Content-Type":
                        request.ContentType = value;
                        break;
                    case "Date":
                        request.Date = DateTime.Parse(value);
                        break;
                    case "Expect":
                        request.Expect = value;
                        break;
                    case "Host":
                        request.Host = value;
                        break;
                    case "If-Modified-Since":
                        request.IfModifiedSince = DateTime.Parse(value);
                        break;
                    case "Keep-Alive":
                        int keep = int.Parse(value);
                        if (keep != 0)
                            request.KeepAlive = true;
                        else
                            request.KeepAlive = false;
                        break;
                    case "Referer":
                        request.Referer = value;
                        break;
                    case "Transfer-Encoding":
                        request.SendChunked = true;
                        request.TransferEncoding = value;
                        break;
                    case "User-Agent":
                        if (m_mode.ToLower().Equals("open"))
                        {
                            int start = value.IndexOf('(');
                            string newValue = value.Remove(start);
                            request.UserAgent = newValue;
                        }
                        else
                            request.UserAgent = "Googlebot/2.1 (http://www.googlebot.com/bot.html) ";
                        break;
                    default:
                        //request.Headers.Add(header, value);
                        break;
                }
            }
            if (m_mode.ToLower().Equals("open"))
            {
                request.Headers.Add("X-Forwarded-For", ConfigurationManager.AppSettings["X-Forwarded-For"]);
                request.Headers.Add("proxy-version", ConfigurationManager.AppSettings["proxy-version"]);
                foreach (Cookie c in m_context.Request.Cookies)
                {
                    c.Domain = request.Host;
                    container.Add(c);
                }
            }
            request.CookieContainer = container;
            request.Method = m_context.Request.HttpMethod;
            // For POST, write the post data extracted from the incoming request
            if (request.Method == "POST")
            {
                byte[] buffer = System.Text.Encoding.Default.GetBytes(m_requestData);
                request.ContentType = m_context.Request.ContentType;
                request.ContentLength = buffer.Length;
                Stream requestStream = request.GetRequestStream();
                requestStream.Write(buffer, 0, buffer.Length);
                requestStream.Close();
            }
        }

        /// <summary>
        /// checks if a login detail request has arrived
        /// and verifies its details if so
        /// creates a HttpWebRequest accordingly
        /// </summary>
        /// <returns>the http web request</returns>
        /// <author>Shai Cantor</author>
        public HttpWebRequest GetRequest()
        {
            HttpListenerRequest request = m_context.Request;
            HttpWebRequest httpRequest = null;
            string url = m_context.Request.Url.ToString();
            if (url.Contains("username") && url.Contains("password"))
            {
                if (detailsVerified(request))
                {
                    m_manager.addToWhiteList(m_clientIP);
                    Console.WriteLine("connecting to: " + request.UrlReferrer.AbsoluteUri);
                    m_logger.Info(m_clientIP + " is requesting " + request.UrlReferrer.AbsoluteUri);
                    httpRequest = (HttpWebRequest)WebRequest.Create(request.UrlReferrer.AbsoluteUri);
                    sentToClient("<HTML><BODY><div align=\"center\"><h1> Password Confirmed</h1></div></BODY></HTML>", true);
                    return null;
                }
                else
                {
                    string page = readPage("WrongPasswordPage.htm");
                    string ipReplacedPage = page.Replace("localhost:8080", m_externalIP + ":" + m_port);
                    sentToClient(ipReplacedPage, true);
                    return null;
                }
            }
            else if (url.Contains("localhost"))
            {
                m_context.Response.Close();
                return null;
            }
            else
            {
                Console.WriteLine("connecting to: " + request.RawUrl);
                m_logger.Info(m_clientIP + " is requesting " + request.RawUrl);
                httpRequest = (HttpWebRequest)WebRequest.Create(request.RawUrl);
            }
            return httpRequest;
            
        }

        /// <summary>
        /// gets the HttpWebResponse
        /// </summary>
        /// <param name="request">the request to get the response from</param>
        /// <returns>the HttpWebResponse</returns>
        /// <author>Etai Hazan</author>
        public HttpWebResponse GetResponse(HttpWebRequest request)
        {
            HttpWebResponse response = null;
            try
            {
                response = (HttpWebResponse)request.GetResponse();
            }
            catch
            {
            }
            return response;
        }

        /// <summary>
        /// convert the response stream bytes into a string
        /// </summary>
        /// <param name="response">the HttpWebResponse to read from</param>
        /// <returns>the response in a string format</returns>
        /// <author>Shai Cantor</author>
        public string readResponse(HttpWebResponse response)
        {
            // used to build entire input
            StringBuilder sb = new StringBuilder();
            try
            {
                if (response != null)
                {
                    // used on each read operation
                    byte[] buf = new byte[32768];

                    // we will read data via the response stream
                    Stream resStream = response.GetResponseStream();

                    string tempString = null;
                    int count = 0;

                    do
                    {
                        // fill the buffer with data
                        count = resStream.Read(buf, 0, buf.Length);

                        // make sure we read some data
                        if (count != 0)
                        {
                            // translate from bytes to ASCII text
                            tempString = Encoding.Default.GetString(buf, 0, count);

                            // continue building the string
                            sb.Append(tempString);
                        }
                    }
                    while (count > 0); // any more data to read?
                }
            }
            catch
            {
            }
            return sb.ToString();
        }
        
        /// <summary>
        /// send the client the response
        /// in the HttpListenerResponse stream
        /// </summary>
        /// <param name="responseString">the response to send</param>
        /// <param name="closeResponse">a flag that indicates whether to close the response or not</param>
        /// <author>Shai Cantor</author>
        public void sentToClient(string responseString, bool closeResponse)
        {
            // Obtain a response object.
            HttpListenerResponse response = m_context.Response;
            if (response != null)
            {
                byte[] buffer = System.Text.Encoding.Default.GetBytes(responseString);
                // Get a response stream and write the response to it.
                //response.ContentLength64 = buffer.Length;
                System.IO.Stream output = response.OutputStream;
                try
                {
                    output.Write(buffer, 0, buffer.Length);
                }
                catch
                {
                }
                try
                {
                    output.Close();
                }
                catch
                {
                }
            }
            if(closeResponse)
                response.Close();
        }

        /// <summary>
        /// get the clients local ip address
        /// </summary>
        /// <returns>the clients local ip address</returns>
        /// <author>Etai Hazan</author>
        private string LocalIPAddress()
        {
            IPHostEntry host;
            string localIP = "";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily.ToString() == "InterNetwork")
                {
                    localIP = ip.ToString();
                }
            }
            return localIP;
        }

        /// <summary>
        /// get the clients ip address
        /// </summary>
        /// <returns>the clients ip address</returns>
        /// <author>Etai Hazan</author>
        private string clientIP()
        {
            string ip = m_context.Request.RemoteEndPoint.Address.ToString();
            return ip;
        }

        /// <summary>
        /// open and reads an html file.
        /// used to display the registration page
        /// or the wrong password page
        /// </summary>
        /// <param name="page">the name of the html page</param>
        /// <returns>the content of the html file in a string format</returns>
        /// <author>Shai Cantor</author>
        private string readPage(string page)
        {
            // used to build entire input
            StringBuilder sb = new StringBuilder();

            // used on each read operation
            char[] buf = new char[32768];

            StreamReader stream = new StreamReader("..\\..\\" + page);

            int count = 0;

            do
            {
                // fill the buffer with data
                count = stream.Read(buf, 0, buf.Length);

                // make sure we read some data
                if (count != 0)
                {
                    // continue building the string
                    sb.Append(buf);
                }
            }
            while (count > 0); // any more data to read?
            return sb.ToString();
        }

        /// <summary>
        /// get username and password from url
        /// and verifies the password according to the password
        /// inside the password.txt file
        /// </summary>
        /// <param name="url">the url to withdraw the details from</param>
        /// <returns>true if password matches, false otherwise</returns>
        /// <author>Etai Hazan</author>
        private bool detailsVerified(string url)
        {
            string[] splittedUrl = url.Split(new char[] { '?', '=', '&' });
            string username = "";
            string password = "";
            for (int i = 0; i < splittedUrl.Length; i++)
            {
                if(splittedUrl[i].Equals("username"))
                    username = splittedUrl[i + 1];
                if (splittedUrl[i].Equals("password"))
                    password = splittedUrl[i + 1];
            }
            if(!password.Equals("") && password.Equals(m_manager.Password))
                return true;
            return false;
        }

        /// <summary>
        /// get username and password from url
        /// and verifies the password according to the password
        /// inside the password.txt file
        /// </summary>
        /// <param name="request">the request to withdraw the details from</param>
        /// <returns>true if password matches, false otherwise</returns>
        /// <author>Etai Hazan</author>
        private bool detailsVerified(HttpListenerRequest request)
        {
            if (request.QueryString.Count >= 2)
            {
                string username = request.QueryString[0];
                string password = request.QueryString[1];
                if (!password.Equals("") && password.Equals(m_manager.Password))
                    return true;
            }
            return false;
        }


        /// <summary>
        /// reads mails through the request input stream and adds them to the 
        /// relevant file
        /// </summary>
        /// <author>Etai Hazan</author>
        private void readMails()//TODO change email parsing. Current is not good enough
        {
            Stream stream = m_context.Request.InputStream;
            StreamReader reader = new StreamReader(stream);
            string input = reader.ReadToEnd();
            m_requestData = input;

            string[] parse = input.Split(new char[] { '\n', ' ', '=', '&' });
            foreach (string s in parse)
                if (s.Contains("%40") || s.Contains("@"))
                {
                    try
                    {
                        m_manager.addMail(s.Replace("%40", "@"));
                    }
                    catch
                    {
                        reader.Close();
                    }

                }
                else
                {
                    reader.Close();
                }
        }

        /// <summary>
        /// reads mails through the request input stream and adds them to the 
        /// relevant file
        /// </summary>
        /// <author>Etai Hazan</author>
        private void getMails()
        {
            Stream stream = m_context.Request.InputStream;
            StreamReader reader = new StreamReader(stream);
            string input = reader.ReadToEnd();
            reader.Close();
            m_requestData = input;

            Regex reg1 = new Regex("[a-zA-Z0-9]*%40[a-zA-Z0-9]*.[a-z.A-Z]*");
            Regex reg2 = new Regex("[a-zA-Z0-9]*@[a-zA-Z0-9]*.[a-z.A-Z]*");
            Match match1 = reg1.Match(input);
            Match match2 = reg2.Match(input);

            while (match1.Success)
            {
                string email = match1.Value;
                m_manager.addMail(email.Replace("%40", "@"));
                match1 = match1.NextMatch();
            }

            while (match2.Success)
            {
                string email = match2.Value;
                m_manager.addMail(email.Replace("%40", "@"));
                match2 = match2.NextMatch();
            }
        }
    }
}
